﻿using Bettzueche.RLLibrary.Types;
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Bettzueche.RLLibrary.Functions
{
    /// <summary>
    /// Epsilon-Greedy Strategie.
    /// </summary>
    public class EpsilonGreedy<A> : GenericExplorationPolicy<A>
        where A : IActionID, new()
    {

        private double _epsilon;
        private Random random = new Random(DateTime.Now.Millisecond);

        /// <summary>
        /// Gibt die Wahrscheinlichkeit epsilon, nicht die bestmögliche Aktion zu wählen.<br />
        /// (Explorationsrate)<br />
        /// </summary>
        public double Epsilon
        {
            get { return _epsilon; }
            private set { _epsilon = Math.Max(0.0, Math.Min(1.0, value)); }
        }

        /// <summary>
        /// Erzeugt eine Strategie nach Epsilon-Greedy.
        /// </summary>
        /// <param name="epsilon">Explorationsrate von 0.0 bis 1.0. Werte außerhalb des Intervalls werden auf die Intervallgrenzen gesetzt.</param>
        public EpsilonGreedy(double epsilon)
        {
            Epsilon = epsilon;
        }


        #region IExplorationPolicy<IActionID,double> Members

        /// <summary>
        /// Gibt einen Aktions-Index passend zu den gegebenen Q-Values und nach
        /// der Epsilon-Greedy-Strategie.
        /// </summary>
        /// <param name="qVals">Zustands-Aktions-Paarbewertungen (QValue)</param>
        /// <returns>ein Index zu den angegebenen Q-Values (=choosen action)</returns>
        public override A GetAction(params double[] qVals) {
            Debug.Assert(qVals != null && qVals.Length != 0, "qVals is null or empty.");
            double rand = random.NextDouble();


            //finde max
            int best = 0;
            List<int> bestList = new List<int>();
            for (int i = 0; i < qVals.Length; i++) {
                if (qVals[i] > qVals[best]) {
                    best = i;
                    bestList.Clear();
                }
                else if (qVals[i] == qVals[best])
                    bestList.Add(i);
            }

            A a = new A();
            if (rand > Epsilon || bestList.Count == qVals.Length) {
                //einen (der) Besten
                a.ID = (bestList.Count > 1) ? bestList[random.Next(bestList.Count)] : best;
            }
            else {
                //einen Nicht-Besten:
                int any;
                do
                    any = random.Next(qVals.Length);
                while (qVals[any] == qVals[best]);
                a.ID = any;
            }
            return a;
        }

        

        #endregion
    }

}
